package info.rflow.model;

import info.rflow.Utils;
import info.rflow.connectors.ConnectorUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Level;


/**
 * Document me!
 *
 * @author Holger Brandl
 */
public class RBlock {

    private RWorkflowModel wfModel;
    private String blockName;

    private String code = "";

    ArrayList<String> outputs = new ArrayList<String>();
    ArrayList<String> inputs = new ArrayList<String>();

    private Collection<BlockStatusListener> listeners = new ArrayList<BlockStatusListener>();


    public RBlock(RWorkflowModel wfModel, String blockName) {
        this.wfModel = wfModel;
        this.blockName = blockName;
    }


    public String getName() {
        return blockName;
    }


    public RWorkflowModel getWorkflowModel() {
        return wfModel;
    }


    public void addOutput(String outputName) {
        if (!outputs.contains(outputName))
            outputs.add(outputName);
    }


    public void addInput(String inputName) {
        // todo validate that the input is not used as output
        if (!inputs.contains(inputName))
            inputs.add(inputName);
    }


    public void addCodeLine(String line) {
        code = code + "\n" + line;
    }


    public String getCode() {
        return code.trim();
    }


    public ArrayList<String> getOutputs() {
        return outputs;
    }


    public ArrayList<String> getInputs() {
        return inputs;
    }


    public Boolean isExecuted() {
        for (String output : outputs) {
            File file = prefixFileWithWD(output);
            if (file == null) {
                return null;
            }

            if (!file.isFile()) {
                return false;
            }
        }

        return true;
    }


    public Boolean isExecutable() {
        if (getWorkflowModel().getWorkingDirectory() == null)
            return false;

        for (String input : inputs) {
            if (!prefixFileWithWD(input).isFile()) {
                return false;
            }
        }

        return true;
    }


    public File prefixFileWithWD(String output) {
        File workingDirectory = wfModel.getWorkingDirectory();
        if (workingDirectory == null)
            return null;

        return new File(workingDirectory.getAbsolutePath() + File.separatorChar + output);
    }


    public void reset() {
        Utils.log("reset node: " + getName());
        for (String output : outputs) {
            File outputFile = prefixFileWithWD(output);
            if (outputFile.isFile()) {
                outputFile.delete();
            }
        }

        // reset downstream nodes
        for (String outputFileName : outputs) {
            for (RBlock downstreamNode : wfModel.getInputNodes(outputFileName)) {
                downstreamNode.reset();
            }
        }


        for (BlockStatusListener listener : listeners) {
            listener.blockReset(this);
        }
    }


    public void execute() {
        for (BlockStatusListener listener : listeners) {
            listener.executionStarted(this);
        }

        // prepare the code for being pushed to R
        String blockCode = Utils.stripHashCharLines(getCode());
        blockCode = Utils.uncommentLoadStatements(blockCode);

        long execStartTime = System.currentTimeMillis();

        // add the magic number for block execution tracking
        int magicNumber = Utils.getRanGen().nextInt();
        blockCode += "\n" + magicNumber + ROutputListener.RFLOW_MNUM_SUFFIX;

        // push the block to R
        ConnectorUtils.push2R(blockCode);

        //todo make this a preference and/or replace with a magic number approach
//        int maxSleepTime = 60000, sleepTime = 250;
//        while (!isExecuted() && maxSleepTime > 0) {
//            maxSleepTime = -sleepTime;
//            Utils.sleep(sleepTime);
//        }
        ROutputListener.wait4MagicNumberOrTimeout(magicNumber);

        getWorkflowModel().getOutputCache().update(getName(), getNodeOutputs(execStartTime));

        if (!isExecuted()) {
            Utils.log(Level.SEVERE, "Execution of block '" + getName() + "' failed!");
        }

        for (BlockStatusListener listener : listeners) {
            listener.executionFinished(this);
        }
    }


    public List<File> getNodeOutputs(long execStartTime) {
        List<File> modFiles = new ArrayList<File>();

        File cwd = getWorkflowModel().getWorkingDirectory();
        for (File file : cwd.listFiles()) {
            if (file.lastModified() > execStartTime)
                modFiles.add(file);
        }

        return modFiles;
    }


    public void addBlockStatusListener(BlockStatusListener blockExecutionListener) {
        listeners.add(blockExecutionListener);
    }


    public void setCode(String code) {
        if (code.equals(this.code))
            return;

        this.code = code;
        this.wfModel.setChanged(true);
    }


    public String getBlockCode() {
//        StringBuilder sb = new StringBuilder();
//
//        if (!blockName.equals(RFileParser.HEADER_BLOCK_NAME)) {
////            sb.append("########################################################################################################################\n");
//            sb.append("#### " + blockName + "\n");
////            sb.append("########################################################################################################################\n");
//            sb.append("\n" + getCode() + "\n\n");
//        }

        return getCode() + "\n";
    }


    @Override
    public String toString() {
        return blockName;
    }


    public Collection<RBlock> getDependencies() {
        HashSet<RBlock> deps = new HashSet<RBlock>();

        //todo check for circular dependencies here
        for (String input : inputs) {
            RBlock outputBlock = wfModel.getOuputNode(input);
            deps.add(outputBlock);

            deps.addAll(outputBlock.getDependencies());
        }

        return deps;
    }
}
